#include "MsnMessage.h"
#include "Util.h"

#include "MsnCommand.h"
#include "MsnConnection.h"


MsnMessageFactory MsnMessageFactory::instance;

MsnMessageFactory::MsnMessageFactory()
{
	messageTypeMap[_T("text/x-msmsgsprofile")] = _T("ProfileMessage");
	messageTypeMap[_T("text/x-msmsgscontrol")] = _T("ControlMessage");
	messageTypeMap[_T("text/plain")] = _T("InstantMessage");
	messageTypeMap[_T("text/x-msmsgsinvite")] = _T("InviteMessage");
	messageTypeMap[_T("application/x-msnmsgrp2p")] = _T("P2PMessage");
}

wxString MsnMessageFactory::GetContentType(const wxString& chunk)
{
    int start = chunk.find(_T("Content-Type: "));
    int end = chunk.find(_T("\r\n"),start);

    wxString contentType = chunk.SubString(start+13,end-1).Trim(false).Trim(true);
    contentType = contentType.BeforeFirst(';');
    return contentType;
}

wxString MsnMessageFactory::GetHeader(char* buf, int len)
{
	bool returnMet = false;
    int i = 0;
    for(; i<len; i++){
		if(buf[i] == 10){
			if(returnMet){
				break;
			}else{
				returnMet = true;
			}
		}else if(buf[i] == 13){
            continue;
		}else{
			returnMet = false;
		} 
    }

    return wxString(buf, wxConvUTF8, i + 1);
}

MsnMessage* MsnMessageFactory::ParseMessage(char* buf, int len)
{
	wxString header = GetHeader(buf, len);

	wxString contentType = GetContentType(header);
	wxString messageType = messageTypeMap[contentType];

    MsnMessage* msg = (MsnMessage*)wxCreateDynamicObject(messageType);


	if(msg){
        msg->Decode(buf, len);
	}else{
		DEBUG(_T("Unknown message type: ") + contentType);
	}

    return msg;
}

MsnMessage* MsnMessageFactory::ParseMessage(MessageBuffer& buffer)
{
    DEBUG(_T("Parse Message ...."));

    wxString header = buffer.ReadHeader();

    wxString contentType = GetContentType(header);
	wxString messageType = messageTypeMap[contentType];

    MsnMessage* msg = (MsnMessage*)wxCreateDynamicObject(messageType);

/*
    if(contentType.IsSameAs(_T("text/x-msmsgsprofile"))){
        msg = new ProfileMessage();
    }else if(contentType.IsSameAs(_T("text/x-msmsgscontrol"))){
        msg = new ControlMessage();
    }else if(contentType.IsSameAs(_T("text/plain"))){
        msg = new InstantMessage();
    }else if(contentType.IsSameAs(_T("text/x-msmsgsinvite"))){
        msg = new InviteMessage();
    }else if(contentType.IsSameAs(_T("application/x-msnmsgrp2p"))){
        //msg = new P2PMessage()
        unsigned char* bin = (unsigned char*)buffer.GetBuffer();

        DEBUG(StringUtil::ToHex(&bin[buffer.GetOffset()], 48));

        buffer.SetOffset(buffer.GetOffset()+48);

        DEBUG(_T("========================"));
        DEBUG(buffer.ReadBody());
        DEBUG(_T("========================"));

        return NULL;
    }else{
        DEBUG(_T("Unknown message type: ")+contentType);
    }
*/

    if(msg){
        msg->Decode(buffer.GetBuffer(), buffer.GetSize());
	}else{
		DEBUG(_T("Unknown message type: ")+contentType);
	}

    return msg;
}



MessageBuffer::MessageBuffer(unsigned int _size)
{
    offset = 0;
    size = _size;
    buf = new char[size];
}
MessageBuffer::~MessageBuffer()
{
    delete[] buf;
}

wxString MessageBuffer::ReadLine()
{
    int start = offset;

    while(offset < size){
        if(buf[offset] == 10){
            offset ++;
            break;
        }
        offset ++;
    }

    return wxString(&buf[start], wxConvUTF8, offset-start);
}

wxString MessageBuffer::ReadBody()
{
    return wxString(&buf[offset], wxConvUTF8, size - offset);
}

wxString MessageBuffer::ReadHeader()
{
    wxString header;
    wxString line = ReadLine();

    while(!line.IsEmpty()){
        header<<line;

        if(line.Trim(true).IsEmpty()) break;

        line = ReadLine();
    }

    return header;
}

wxString MessageBuffer::ReadString(unsigned int len)
{
    int start = offset;
    int actual = size-offset > len?len:size-offset;

    offset += actual;

    return wxString(&buf[start], wxConvUTF8, actual);
}


MsnMessage::MsnMessage() : ackType(_T("N"))
{
	createTime = wxDateTime::GetTimeNow();
}

MsnMessage::MsnMessage(const MsnMessage& msg)
{
	DEBUG(_T("MsnMessage copy constructor"));

	headProperties = msg.headProperties;
	bodyProperties = msg.bodyProperties;
}

wxString MsnMessage::GetHeadProperty(const wxString& name)
{
	for(StringPairVector::iterator it = headProperties.begin();it!=headProperties.end();++it){
		if(name.IsSameAs(it->first)){
			return it->second;
		}
	}

	return wxEmptyString;
}

void MsnMessage::SetHeadProperty(const wxString& name, const wxString& value)
{
	headProperties.push_back(std::make_pair(name, value));

}

wxString MsnMessage::GetProperty(const wxString& name)
{
	for(StringPairVector::iterator it = bodyProperties.begin();it!=bodyProperties.end();++it){
		if(name.IsSameAs(it->first)){
			return it->second;
		}
	}

	return wxEmptyString;
}

bool MsnMessage::GetPropertyAsBool(const wxString& name)
{
	wxString result = GetProperty(name);

	return result.IsSameAs(_T("true"), false);
}

void MsnMessage::SetProperty(const wxString& name, const wxString& value)
{
	bodyProperties.push_back(std::make_pair(name, value));
}


void MsnMessage::DecodeProperties(StringPairVector& vec, const wxString& props)
{
    wxStringTokenizer st(props,_T("\r\n"));

    while(st.HasMoreTokens()){
        wxString line = st.NextToken();

        wxString name = line.BeforeFirst(':').Trim(false).Trim(true);
        wxString value = line.AfterFirst(':').Trim(false).Trim(true);

        vec.push_back(std::make_pair(name, value));
    }
}

void MsnMessage::EncodeProperties(StringPairVector& vec, wxMemoryBuffer& buf)
{
	wxString result;

    StringPairVector::iterator it = vec.begin();
    while(it != vec.end()){
        wxString name = it->first;
        wxString value = it->second;
        result<<name<<_T(": ")<<value<<_T("\r\n");
        it++;
    }

    buf.AppendData(WS2C(result), WSLEN(result));	
}

void MsnMessage::Decode(char* buf, unsigned int len)
{
	int pos = StringUtil::IndexOf(buf, len, "\r\n\r\n", 4);
    if(pos > 0){
        DecodeHead(buf, pos);
        DecodeBody(&buf[pos+4], len - pos -4);
    }else{
        DecodeBody(buf, len);
    }
}

void MsnMessage::DecodeHead(char* buf, unsigned int len)
{
	DecodeProperties(headProperties, wxString(buf, wxConvUTF8, len));
}

void MsnMessage::DecodeBody(char* buf, unsigned int len)
{
	DecodeProperties(bodyProperties, wxString(buf, wxConvUTF8, len));
}

void MsnMessage::Encode(wxMemoryBuffer& buf)
{
	EncodeHead(buf);
	
	buf.AppendData("\r\n", 2);

	EncodeBody(buf);
}

void MsnMessage::EncodeHead(wxMemoryBuffer& buf)
{
    wxString result;

    result<<_T("MIME-Version: 1.0\r\n");
    result<<_T("Content-Type: ")<<messageType<<_T("; charset=UTF-8\r\n");

	buf.AppendData(WS2C(result), WSLEN(result));
}

void MsnMessage::EncodeBody(wxMemoryBuffer& buf)
{
	EncodeProperties(bodyProperties, buf);
}


wxString MsnMessage::ToString()
{
    wxMemoryBuffer buf;
    Encode(buf);
    return wxString((char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());
}

size_t MsnMessage::GetMessageLength()
{
	wxMemoryBuffer buf;
    Encode(buf);
	return buf.GetDataLen();	
}


size_t MsnMessage::GetBodyLength()
{
	wxMemoryBuffer buf;
    EncodeBody(buf);
	return buf.GetDataLen();
	
}


IMPLEMENT_DYNAMIC_CLASS(ProfileMessage, MsnMessage)

void ProfileMessage::DecodeBody(char* buf, unsigned int len)
{
	DecodeProperties(bodyProperties, wxString(buf, wxConvUTF8, len));
}

IMPLEMENT_DYNAMIC_CLASS(InstantMessage, MsnMessage)

InstantMessage::InstantMessage():
	underline (false), strikeout(false),italic(false),bold(false),colorBlue(0),colorRed(0),colorGreen(0)
{
	 ackType = (_T("A"));
	messageType = _T("text/plain");
}

void InstantMessage::DecodeBody(char* buf, unsigned int len)
{
    content = wxString(buf, wxConvUTF8, len);

    //parse format
    wxString format = GetHeadProperty(_T("X-MMS-IM-Format"));
    wxStringTokenizer st(format,_T(";"));
    while(st.HasMoreTokens()){
        wxString token = st.NextToken();
        wxString key = token.BeforeFirst('=').Trim(false).Trim(true);
        wxString value = token.AfterFirst('=').Trim(false).Trim(true);

        if(key.IsSameAs(_T("CS"))){
            //CS = value;
            //Debug(key+_("............")+value);
        }else if(key.IsSameAs(_T("FN"))){
            fontName = StringUtil::URLDecode(value);
        }else if(key.IsSameAs(_T("EF"))){
            bold = value.Find('B') > -1;
            italic = value.Find('I') > -1;
            underline = value.Find('U') > -1;
            strikeout = value.Find('S') > -1;
        }else if(key.IsSameAs(_T("CO"))){
            if(value.Length() > 6) value = value.Mid(value.Length()-6);
            if(value.Length() < 6) value = value.Pad(6 - value.Length(), '0', false);

            colorBlue = StringUtil::ToLong(value.Mid(0,2), 16);
            colorGreen = StringUtil::ToLong(value.Mid(2,2), 16);
            colorRed = StringUtil::ToLong(value.Mid(4,2), 16);
        }else if(key.IsSameAs(_T("PF"))) {
             fontPF = value;
        }else{

        }
    }

}

void InstantMessage::EncodeHead(wxMemoryBuffer& buf)
{
    MsnMessage::EncodeHead(buf);

	wxString result;
    result<<_T("X-MMS-IM-Format: ");
    result<<_T("FN=")<<StringUtil::URLEncode(fontName)<<_T(";");
    result<<_T("EF=;");
    result<<_T("CO=")<<StringUtil::ToHex(colorBlue)<<StringUtil::ToHex(colorGreen)<<StringUtil::ToHex(colorRed)<<_T(";");
    result<<_T("CS=;");
    result<<_T("PF=;");
    result<<_T("\r\n");

	buf.AppendData(WS2C(result), WSLEN(result));
}

void InstantMessage::EncodeBody(wxMemoryBuffer& buf)
{
    buf.AppendData(WS2C(content), WSLEN(content));
}


IMPLEMENT_DYNAMIC_CLASS(ControlMessage, MsnMessage)

void ControlMessage::EncodeHead(wxMemoryBuffer& buf)
{
	MsnMessage::EncodeHead(buf);

	wxString result = _T("TypingUser: tuxitty@hotmail.com\r\n");

    buf.AppendData(WS2C(result), WSLEN(result));
}


IMPLEMENT_DYNAMIC_CLASS(InviteMessage, MsnMessage)

InviteMessage::InviteMessage()
{
	ackType = (_T("A"));
	messageType = _T("text/x-msmsgsinvite");
}

void InviteMessage::DecodeBody(char* buf, unsigned int len)
{
	wxString str = wxString(buf, wxConvUTF8, len);
	DecodeProperties(bodyProperties, str);
}

void InviteMessage::EncodeBody(wxMemoryBuffer& buf)
{
    wxString result;

    StringPairVector::iterator it = bodyProperties.begin();

    while(it != bodyProperties.end()){
        wxString name = it->first;
        wxString value = it->second;
        result<<name<<_T(": ")<<value<<_T("\r\n");
        it++;
    }
    buf.AppendData(WS2C(result), WSLEN(result));
}	


wxString P2PHeader::ToReadableFormat()
{
	wxString data = _T("\r\nBinary Header:");
	data<<_T("sessionID=")<<sessionID<<_T(",");
	data<<_T("ID=")<<ID<<_T(",");
	data<<_T("dataOffset=")<<dataOffset<<_T(",");
	data<<_T("dataSize=")<<dataSize<<_T(",");
	data<<_T("messageLength=")<<messageLength<<_T(",");
	data<<_T("flag=")<<flag<<_T(",");
	data<<_T("ackID=")<<ackID<<_T(",");
	data<<_T("ackUID=")<<ackUID<<_T(",");
	data<<_T("ackDataSize=")<<ackDataSize<<_T("\r\n");	

	return data;
}


IMPLEMENT_DYNAMIC_CLASS(P2PMessage, MsnMessage)

P2PMessage::P2PMessage() : slpMessage(0), footer(0)
{
	ackType = (_T("D"));
	messageType = _T("application/x-msnmsgrp2p");
}

P2PMessage::P2PMessage(const P2PMessage& msg) 
{
	DEBUG(_T("P2PMessage copy constructor"));

	memcpy(&header, &(msg.header), sizeof(P2PHeader));

	slpBuffer = msg.slpBuffer;

	if(msg.slpMessage != NULL){
		slpMessage = new SLPMessage(*(msg.slpMessage));
	}
}


P2PMessage::~P2PMessage()
{	
	wxDELETE(slpMessage);
	//DEBUG(_T("~P2PMessage"));
}

void P2PMessage::DecodeSlpMessage()
{
	slpMessage = new SLPMessage();
	slpMessage->Decode((char*)slpBuffer.GetData(), slpBuffer.GetDataLen());
}

void P2PMessage::DecodeBody(char* body, unsigned int len)
{
	//DEBUG(_T("decode P2P message"));

	DecodeBinaryHeader(body);

	int msgLen = header.messageLength;

	if(msgLen > 0){
		/*
			DEBUG(_T("\r\n############################################################################################################################\r\n") 
					+ wxString(&body[48],  wxConvUTF8, msgLen) + 
					_T("\r\n##########################################################################################################################\r\n"));
		*/
		slpBuffer.AppendData(&body[48], msgLen);
	}

	DecodeBinaryFooter(&body[48 + msgLen]);
}

void P2PMessage::EncodeHead(wxMemoryBuffer& buf)
{
    wxString result;

    result<<_T("MIME-Version: 1.0\r\n");
    result<<_T("Content-Type: ")<<messageType<<_T("\r\n");
	result<<_T("P2P-Dest: ")<<GetDestination() <<_T("\r\n");

	buf.AppendData(WS2C(result), WSLEN(result));
}

void P2PMessage::EncodeBody(wxMemoryBuffer& buf)
{
	DEBUG(_T("encode P2P body"));

	EncodeBinaryHeader(buf);

	//TODO message is split if too big
	if(slpMessage != NULL){
		slpMessage->Encode(buf);
	}else{
		buf.AppendData(slpBuffer.GetData(), slpBuffer.GetDataLen());
	}

	EncodeBinaryFooter(buf);
}

void P2PMessage::DecodeBinaryHeader(char* body)
{
	//DEBUG(StringUtil::ToHexFormat((unsigned char*)body, 48));

	header.sessionID = StringUtil::ReadInt(body, 0, 4);
	header.ID = StringUtil::ReadInt(body, 4, 4);;
	header.dataOffset = StringUtil::ReadInt8(body, 8, 8);
	header.dataSize = StringUtil::ReadInt8(body, 16, 8);
	header.messageLength= StringUtil::ReadInt(body, 24, 4);
	header.flag= StringUtil::ReadInt(body, 28, 4);
	header.ackID= StringUtil::ReadInt(body, 32, 4);
	header.ackUID= StringUtil::ReadInt(body, 36, 4);
	header.ackDataSize = StringUtil::ReadInt8(body, 40, 8);

	//DEBUG(header.ToReadableFormat());
}

void P2PMessage::DecodeBinaryFooter(char* buf)
{

	footer = StringUtil::ReadInt(buf, 0, 4, false);
}

void P2PMessage::EncodeBinaryHeader(wxMemoryBuffer& buf)
{	
	char data[48] = {0};

	StringUtil::WriteInt(data, 0, header.sessionID);
	StringUtil::WriteInt(data, 4, header.ID);
	StringUtil::WriteInt8(data, 8, header.dataOffset);
	StringUtil::WriteInt8(data, 16, header.dataSize);
	StringUtil::WriteInt(data, 24, header.messageLength);
	StringUtil::WriteInt(data, 28, header.flag);
	StringUtil::WriteInt(data, 32, header.ackID);
	StringUtil::WriteInt(data, 36, header.ackUID);
	StringUtil::WriteInt8(data, 40, header.ackDataSize);

	buf.AppendData(data, 48);

	//DEBUG(StringUtil::ToHexFormat((unsigned char*)data, 48));
}

void P2PMessage::EncodeBinaryFooter(wxMemoryBuffer& buf)
{
	//big endian
	char chunk[4] = {0};
	StringUtil::WriteInt(chunk, 0, footer, false);
	buf.AppendData(chunk, 4);
}

wxString P2PMessage::ToString()
{
	wxMemoryBuffer buf;
	EncodeHead(buf);

	wxString result = wxString((char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());

	char data[48] = {0};

	StringUtil::WriteInt(data, 0, header.sessionID);
	StringUtil::WriteInt(data, 4, header.ID);
	StringUtil::WriteInt8(data, 8, header.dataOffset);
	StringUtil::WriteInt8(data, 16, header.dataSize);
	StringUtil::WriteInt(data, 24, header.messageLength);
	StringUtil::WriteInt(data, 28, header.flag);
	StringUtil::WriteInt(data, 32, header.ackID);
	StringUtil::WriteInt(data, 36, header.ackUID);
	StringUtil::WriteInt8(data, 40, header.ackDataSize);

	result << StringUtil::ToHexFormat((unsigned char*)data, 48);

	if(slpMessage != NULL){
		result << _T("\r\n") << slpMessage->ToString();
	}

	return result;
}

/*

00 00 00 00 94 4f 4f 00 00 00 00 00 00 00 00 00 
de 04 00 00 00 00 00 00 b2 04 00 00 00 00 00 00 
e6 c8 89 01 00 00 00 00 00 00 00 00 00 00 00 00 

Header:sessionID=0,identifier=5197716,dataOffset=0,dataSize=1246,messageLength=1202,flag=0,ackIdenfier=25807078,ackUniqueID=0,ackDataSize=0

INVITE MSNMSGR:tuxitty@hotmail.com MSNSLP/1.0
To: <msnmsgr:tuxitty@hotmail.com>
From: <msnmsgr:tuxitty9@hotmail.com>
Via: MSNSLP/1.0/TLP ;branch={25163982-3D2C-48FE-8851-75860FD8F209}
CSeq: 0 
Call-ID: {F78F27A8-107D-4B4C-A679-1A3161E40203}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 904

EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}
SessionID: 23727441
SChannelState: 0
Capabilities-Flags: 1
AppID: 2
Context: PgIAAAIAAAAATgEAAAAAAAEAAABlAG4AZwAuAGQAbwBjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

00 00 00 00 94 4f 4f 00 b2 04 00 00 00 00 00 00 
de 04 00 00 00 00 00 00 2c 00 00 00 00 00 00 00 
f5 c8 89 01 00 00 00 00 00 00 00 00 00 00 00 00 
Header:sessionID=0,identifier=5197716,dataOffset=1202,dataSize=1246,messageLength=44,flag=0,ackIdenfier=25807093,ackUniqueID=0,ackDataSize=0

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////w==

*/

IMPLEMENT_DYNAMIC_CLASS(SLPMessage, MsnMessage)

void SLPMessage::Decode(char* buf, unsigned int len)
{
	DEBUG(_T("decode SLP message"));
	int pos = StringUtil::IndexOf(buf, len, "\r\n", 2);
	method = wxString(buf, wxConvUTF8, pos);

	MsnMessage::Decode(&buf[pos+2], len - pos -2);

	//DEBUG(_T("from == ") + GetHeadProperty(_T("From")));
	//DEBUG(_T("context == ") + GetProperty(_T("Context")));
}

void SLPMessage::Encode(wxMemoryBuffer& buf)
{
	DEBUG(_T("encode SLP message"));
	buf.AppendData(WS2C(method), WSLEN(method));
	buf.AppendData("\r\n", 2);

	//TODO add header properties
	MsnMessage::Encode(buf);
}

void SLPMessage::EncodeHead(wxMemoryBuffer& buf)
{
    //EncodeProperties(headProperties, buf);

	wxString result;

	result<<_T("To: ")<<GetHeadProperty(_T("To"))<<_T("\r\n");
	result<<_T("From: ")<<GetHeadProperty(_T("From"))<<_T("\r\n");
	result<<_T("Via: ")<<GetHeadProperty(_T("Via"))<<_T("\r\n");
	result<<_T("CSeq: ")<<GetHeadProperty(_T("CSeq"))<<_T("\r\n");
	result<<_T("Call-ID: ")<<GetHeadProperty(_T("Call-ID"))<<_T("\r\n");
	result<<_T("Max-Forwards: ")<<GetHeadProperty(_T("Max-Forwards"))<<_T("\r\n");
	result<<_T("Content-Type: ")<<GetHeadProperty(_T("Content-Type"))<<_T("\r\n");
	result<<_T("Content-Length: ")<<GetBodyLength()<<_T("\r\n");
	
	buf.AppendData(WS2C(result), WSLEN(result));

}

void SLPMessage::EncodeBody(wxMemoryBuffer& buf)
{
	MsnMessage::EncodeBody(buf);

	buf.AppendData("\r\n", 2);

	char zero[1] = {0};
	buf.AppendData(zero, 1);
}


wxString SLPMessage::ToString()
{
	wxMemoryBuffer buf;
	Encode(buf);

	return wxString((char*)buf.GetData(), wxConvUTF8, buf.GetDataLen());
}

